/*
 * Copyright (C) 2019 Rockchip Electronics Co., Ltd.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL), available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>

#include <linux/netlink.h>
#include <sys/socket.h>
#include <sys/time.h>

#include "uevent.h"
#include "uvc_control.h"
#include "uvc_log.h"

extern int app_quit;

static void handle_uvc_event(struct uevent *uevent)
{
    if (strcmp(uevent->subsystem,"android_usb"))
        return;

    if (!strcmp(uevent->usb_state,"DISCONNECTED")) {
        LOG_INFO("udc disconnected\n");
        app_quit = 1;
    } else if (!strcmp(uevent->usb_state,"CONNECTED")) {
        LOG_INFO("udc connected\n");
    } else if (!strcmp(uevent->usb_state,"CONFIGURED")) {
        LOG_INFO("udc configured\n");
    } else {
        LOG_INFO("unknow usb event\n");
    }
}

static void parse_event(const char *msg, struct uevent *uevent)
{
    uevent->action = "";
    uevent->path = "";
    uevent->subsystem = "";
    uevent->usb_state = "";
    uevent->device_name = "";

    while(*msg) {
        if(!strncmp(msg, "ACTION=", 7)) {
            msg += 7;
            uevent->action = msg;
        } else if(!strncmp(msg, "DEVPATH=", 8)) {
            msg += 8;
            uevent->path = msg;
        } else if(!strncmp(msg, "SUBSYSTEM=", 10)) {
            msg += 10;
            uevent->subsystem = msg;
        } else if(!strncmp(msg, "USB_STATE=", 10)) {
            msg += 10;
            uevent->usb_state = msg;
        } else if(!strncmp(msg, "DEVNAME=", 8)) {
            msg += 8;
            uevent->device_name = msg;
        }
        /* advance to after the next \0 */
        while(*msg++)
            ;
    }

    LOG_DEBUG("event { '%s', '%s', '%s', '%s', '%s' }\n",
         uevent->action, uevent->path, uevent->subsystem, uevent->usb_state, uevent->device_name);
    handle_uvc_event(uevent);
}

static void *event_monitor_thread(void *arg)
{
    int sockfd;
    int i, j, len;
    char buf[1024 + 2];
    struct iovec iov;
    struct msghdr msg;
    struct sockaddr_nl sa;
    struct uevent uevent;
    uint32_t flags = *(uint32_t *)arg;

    prctl(PR_SET_NAME, "event_monitor", 0, 0, 0);

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;
    sa.nl_groups = NETLINK_KOBJECT_UEVENT;
    sa.nl_pid = 0;
    memset(&msg, 0, sizeof(msg));
    iov.iov_base = (void *)buf;
    iov.iov_len = sizeof(buf);
    msg.msg_name = (void *)&sa;
    msg.msg_namelen = sizeof(sa);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    if (sockfd == -1) {
        LOG_ERROR("socket creating failed:%s\n", strerror(errno));
        goto err_event_monitor;
    }

    if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
        LOG_ERROR("bind error:%s\n", strerror(errno));
        goto err_event_monitor;
    }

    while (1) {
        len = recvmsg(sockfd, &msg, 0);
        if (len < 0) {
            LOG_ERROR("receive error\n");
        } else if (len < 32 || len > sizeof(buf)) {
            LOG_INFO("invalid message");
        } else {
            buf[len] = '\0';
            buf[len + 1] = '\0';
            parse_event(buf, &uevent);
        }
    }

err_event_monitor:
    pthread_detach(pthread_self());
    pthread_exit(NULL);
}

int uevent_monitor_run(uint32_t flags)
{
    pthread_t tid;

    return pthread_create(&tid, NULL, event_monitor_thread, &flags);
}
